/*
 * AsynSocket.cpp
 *
 *  Created on: 2012-12-14
 *      Author: yangentao@gmail.com
 */

#include "AsynSocket.h"
#include "../Log.h"

namespace yet {
//=================================event socket===============
AsynSocket::AsynSocket() {
	init(0);
}
AsynSocket::AsynSocket(SOCKET s) {
	init(s);
}
AsynSocket::~AsynSocket() {
	WSACloseEvent(selectEvent);
}
void AsynSocket::init(SOCKET s) {
	baseSock.sock = s;
	selectEvent = WSACreateEvent();
	connected = false;
}

void AsynSocket::setHandler(boost::shared_ptr<Handler> handler) {
	msgHandler = handler;
}

//FD_READ| FD_WRITE| FD_OOB| FD_ACCEPT| FD_CONNECT| FD_CLOSE
bool AsynSocket::select() {
	return select(FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE);
}
//netEvents: FD_XXX
bool AsynSocket::select(long netEvents) {
	int n = WSAEventSelect(baseSock, selectEvent, netEvents);
	if (n == 0) {
		msgHandler->addHandler(boost::bind(&AsynSocket::messageHandler, this, _1));
		msgHandler->start();
		recvThread.OnRun = boost::bind(&AsynSocket::threadTask, this, _1);
		recvThread.start();
	}
	return n == 0;
}
void AsynSocket::post(Message msg) {
	msgHandler->post(msg);
}
void AsynSocket::close() {
	baseSock.close();
	recvThread.waitFinish(1);
	msgHandler->postQuitMessage();
	msgHandler->waitFinish();
}

int AsynSocket::send(String buf) {
	return baseSock.send(buf.buffer(), buf.size());
}
void AsynSocket::messageHandler(Message& msg) {
	switch (msg.msg) {
	case FD_READ:
		if (OnReceived) {
			OnReceived(this, msg.argS);
		}
		break;
	case FD_OOB:
		if (OnOOBReceived) {
			OnOOBReceived(this, msg.argS);
		}
		break;
	case FD_WRITE:
		if (OnWritable) {
			OnWritable(this);
		}
		break;
	case FD_CONNECT:
		if (OnConnected) {
			OnConnected(this);
		}
		break;
	case FD_ACCEPT:
		if (OnAccept) {
			OnAccept(this);
		}
		break;
	case FD_CLOSE:
		if (OnClosed) {
			OnClosed(this);
		}
		break;
	}

}
DWORD AsynSocket::threadTask(void* argp) {
	while (true) {
		try {
			DWORD dw = WSAWaitForMultipleEvents(1, &selectEvent, FALSE, WSA_INFINITE, FALSE);
			switch (dw) {
			case WSA_WAIT_FAILED : {
				SocketError e;
				Log << "WSAWaitForMultipleEvents Failed: " << e.getCode() << endl;
				return e.getCode();
			}
				break;
			case WSA_WAIT_TIMEOUT: //never reach here , infinite
				break;
			case WSA_WAIT_IO_COMPLETION : //never reach here, fAlertable =false
				break;
			default: {
				if (!processEvents()) { //int index = dw - WSA_WAIT_EVENT_0;
					return 0;
				}
				break;
			}
			}
		} catch (SocketError& e) {
			return e.getCode();
		}
	}
	return 0;
}

bool AsynSocket::processEvents() {
	WSANETWORKEVENTS e = { 0 };
	int n = WSAEnumNetworkEvents(baseSock, selectEvent, &e);
	if (n != 0) { //failed
		throwSocketError();
	}
	//n ==0
	long evt = e.lNetworkEvents;
	int* err = e.iErrorCode;

	if (evt & FD_READ) {
		int code = err[FD_READ_BIT];
		if (code) {
			throwSocketError(code);
		}
		String buf(READ_BUF_SIZE);
		int n = baseSock.recv(buf.buffer(), READ_BUF_SIZE);
		if (n > 0) {
			buf.setLength(n);
			post(Message(FD_READ, buf));
		} else if (n == 0) {
			return false;
		} else {
			throwSocketError();
		}
	}

	if (evt & FD_OOB) {
		int code = err[FD_OOB_BIT];
		if (code) {
			throwSocketError(code);
		}
		String buf(READ_BUF_SIZE);
		int n = baseSock.recvOOB(buf.buffer(), READ_BUF_SIZE);
		if (n > 0) {
			buf.setLength(n);
			post(Message(FD_OOB, buf));
		} else if (n == 0) {
			return false;
		} else {
			throwSocketError();
		}
	}
	if (evt & FD_WRITE) {
		int code = err[FD_WRITE_BIT];
		if (code) {
			throwSocketError(code);
		}
		post(Message(FD_WRITE));
	}

	if (evt & FD_ACCEPT) {
		int code = err[FD_ACCEPT_BIT];
		if (code) {
			throwSocketError(code);
		}
		post(Message(FD_ACCEPT));
	}
	if (evt & FD_CONNECT) {
		int code = err[FD_CONNECT_BIT];
		if (code) {
			throwSocketError(code);
		}
		post(Message(FD_CONNECT));
	}
	if (evt & FD_CLOSE) {
		int code = err[FD_CLOSE_BIT];
		if (code) {
			throwSocketError(code);
		}
		post(Message(FD_CLOSE));
		return false;
	}
	return true;
}

} /* namespace yet */
